package io.gitee.binaryfox.firework;

/**
 * FireWorkBuilder 会组装数据成字符串ID
 * 在时钟回拨的时候会上报此次回拨位和最大回拨时间
 * 并且在计算ID的时候会看此回拨位有无回拨情况,有的话会计算一个OFFSET让现在时间+OFFSET来消费未来时间
 * 在回拨的时候会增加回拨位。真正生成的回拨位位Max(自己的回拨位,FireWork上共享的回拨位)
 * 几个措施来共同确保ID生成不重复,代替SNOWFLAKE直接加锁。
 */
public class FireWorkBuilder {
    private FireWork fireWork;

    FireWorkBuilder(FireWork fireWork) {
        this.fireWork = fireWork;
        this.serviceId = FireWorkSubString.of(fireWork.serviceId, 2);
    }

    //序列号最大值
    private final static int LARGEST_SEQ = (int) Math.pow(FireWorkSubString.len(), 5);
    //微服务服务ID[0-3844)[00-zz]
    private FireWorkSubString serviceId;
    private long step = 0;
    //记录上次时间戳 用于判断时钟是否回拨
    private long timestamp = System.currentTimeMillis();
    //时间戳[0-62^8)[00 000 000-zz zzz zzz]到8888年左右
    private FireWorkSubString timestampStr = FireWorkSubString.of(timestamp, 8);
    private long offset = 0;
    private long lastStep = 0;


    private void recalculateOffset(long timestamp) {
        int index = (int) (step % FireWorkSubString.len());
        long before = fireWork.before[index];
        if (before != 0 && before - timestamp >= 0) {
            offset = before - timestamp + 1;
        }
    }


    /**
     * @param sequence 线程间共享的序列号
     * @return
     */
    public String buildId(int sequence) {
        sequence = sequence % LARGEST_SEQ;
        if (sequence < 0) {
            sequence += LARGEST_SEQ;
        }
        //long currentTimeMillis = ClockBackMock.getCurrentTimeMillis();
        long currentTimeMillis = System.currentTimeMillis();
        //判断时钟回拨
        if (currentTimeMillis - timestamp < 0) {
            //线程发现时钟回拨后报告此次回拨
            fireWork.reportStepBack(step, timestamp);
            step += 1;
        }
        step = Math.max(fireWork.flag, step);
        assert step >= 0;
        if (step != lastStep) {
            //如果回拨位发生变化 需要计算偏移量
            //道理上这段代码只会在回拨位发生变化后出现 分支预测器预测为false才是大多数
            recalculateOffset(timestamp);
            lastStep = step;
        }
        //算出时间在对原prefix做add 这样比直接暴力算整个long效率高
        timestampStr.addAtFixLen(currentTimeMillis - timestamp);
        //添加时钟回拨偏移量
        timestampStr.addAtFixLen(offset);

        timestamp = currentTimeMillis;
        return new StringBuilder().append(timestampStr)
                .append(FireWorkSubString.of(step, 1).toString())
                .append(serviceId)
                .append(FireWorkSubString.of(sequence, 5)).toString();
    }
}
